#include <iostream>

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Cairo.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Image.H>

#include "core/error.hpp"
#include "core/geometry.hpp"
#include "core/color.hpp"
#include "core/image.hpp"
#include "core/matrix.hpp"
#include "core/transform.hpp"

#define WINDOW_W (640*1)
#define WINDOW_H (480*1)

void exitFunc()
{
  exit_error();
}

int cTest,numFail;

void test0()
{
  assert(lerp(0.f,1.f,0.f) == 0);
  assert(lerp(0.f,1.f,1.f) == 1);
  assert(lerp(0.f,1.f,0.5) == 0.5);
}

void test1()
{
  assert(Vector(0,0,0).length() == 0.0);
  assert(Vector(1,0,0).length() == 1.0);
  assert(Vector(0,1,0).length() == 1.0);
  assert(Vector(0,0,1).length() == 1.0);
}

void test2()
{
  assert(Vector(0,0,0) == Vector(0,0,0));
  assert(Vector(0,0,0) != Vector(0,1,0));
}

void test3()
{
  assert(Vector(0,0,0) + Vector(1,1,1) == Vector(1,1,1));
  assert(Vector(0,0,0) - Vector(1,1,1) == Vector(-1,-1,-1));
  assert(Vector(1,1,1) + Vector(0,0,0) == Vector(1,1,1));
  assert(Vector(1,1,1) - Vector(0,0,0) == Vector(1,1,1));
}

void test4()
{
  Vector tmp(0,0,0);
  tmp += Vector(1,1,1);
  assert(tmp == Vector(1,1,1));
  tmp -= Vector(0,0,1);
  assert(tmp == Vector(1,1,0));
  tmp *= Vector(2,2,2);
  assert(tmp == Vector(2,2,0));
  tmp /= Vector(2,2,2);
  assert(tmp == Vector(1,1,0));
  tmp *= 2.0;
  assert(tmp == Vector(2,2,0));
  tmp /= 2.0;
  assert(tmp == Vector(1,1,0));
}

void test5()
{
  assert(dot(Vector(1,0,0), Vector(0,1,0)) == 0.0);
}

void test6()
{
  assert(cross(Vector(1,0,0), Vector(0,1,0)) == Vector(0,0,1));
}

void test7()
{
  assert(Point(1,2,3) + (Point(3,2,1) - Point(1,2,3)) == Point(3,2,1));
}

void test8()
{
  assert(Ray(Point(0,0,1), Vector(0,1,0))(1) == Point(0,1,1));
}

void test9()
{
  assert(Color(1,1,1)+Color(1,1,1) == Color(2,2,2));
  assert(Color(1,1,1)*2.f == Color(2,2,2));
  assert(Color(1,1,1)/2.f == Color(.5,.5,.5));
  assert(Color(1,1,1)-Color(1,1,1) == Color(0,0,0));
  assert(Color(1,1,1)/Color(1,1,1) == Color(1,1,1));
}

void test10()
{
  assert(transforms::identity() * Point(1,1,1) == Point(1,1,1));
}

void test11()
{
  assert(transforms::identity().transpose() == transforms::identity());
  assert(Matrix(1,2,3,4,
		5,6,7,8,
		9,10,11,12,
		1,2,3,4).transpose().transpose() == Matrix(1,2,3,4,
							   5,6,7,8,
							   9,10,11,12,
							   1,2,3,4));
}

void test12()
{
  assert(transforms::identity().inverse() == transforms::identity());
}

void test13()
{
  assert(Point(1,1,1) * transforms::translate(Vector(1,1,1)) == Point(2,2,2));
  assert(Point(1,1,1) * transforms::scale(Vector(2,2,2)) == Point(2,2,2));
}

std::vector<void (*)(void)> tests({test0, test1, test2, test3, test4, test5, test6, test7, test8, test9, test10, test11, test12, test13});

Image* img;

class MyWindow: public Fl_Window
{
private:

  Fl_RGB_Image* i_main;
  Fl_Button* b_render;
  Fl_Box* box_img;

public:

  MyWindow() : Fl_Window(WINDOW_W, WINDOW_H)
  {
    b_render = new Fl_Button(10,10,70,30,"Render");
    i_main = new Fl_RGB_Image(img->buffer_uchar, img->w, img->h);
    box_img = new Fl_Box(10,10, 512,512);
    box_img->image(i_main);
  }

  ~MyWindow()
  {
    delete b_render;
    delete i_main;
    delete box_img;
  }

};
  
int main(int argc, char** argv)
{

  if(argc>1)
    {
      if(argv[1] = "test")
	{
	  isTesting = true;
	}
    }
  init_error();
  atexit(exitFunc);

  if(!isTesting)
    {
      img = new Image(256,256);
      img->updateUchar();
      MyWindow *window = new MyWindow;
      window->show(argc, argv);
      int status = Fl::run();
      delete window, img;
      return status;
    }
  else
    {
      //run tests
      numFail = 0;
      for(cTest=0; cTest<tests.size(); cTest++)
	{
	  try
	    {
	      tests[cTest]();
	      info("[Test "+asString(cTest)+"]"+std::string(GREEN)+" OK");
	    }
	  catch(int)
	    {
	      numFail++;
	      info("[Test "+asString(cTest)+"]"+std::string(RED)+" FAIL");
	    }
	}
      if(numFail > 0)
	{
	  warning(asString(numFail) + " tests failed");
	}
      return 0;
    }
}
